// Copyright 2011 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


/**
 * @fileoverview Contains a set of helper function for downloading layer and
 * script content.
 *
 * Public functions:
 *   shellExtension.reload.layers(layers, userData, completeHandler, log) -
 *       Downloads the given layers' content and sets the data.  It will also
 *       cause all related scripts that load immediately to be downloaded as
 *       well.
 *   shellExtension.reload.scripts(layers, completeHandler, log, opt_data) -
 *       Downloads a layer's scripts if the layer is on and is set to either
 *       load immediately or the url matches the scripts match pattern.
 *
 * @author jasonstredwick@google.com (Jason Stredwick)
 */


goog.provide('shellExtension.reload');

goog.require('bite.common.log.Simple');
goog.require('bite.common.net.xhr.async');
goog.require('shellExtension.Layer');
goog.require('shellExtension.data');
goog.require('shellExtension.manifest');
goog.require('shellExtension.status');


/**
 * Defines settings that can be used to override those that were fetched.
 * @typedef {{ondemand: shellExtension.manifest.layer.State,
 *            state: shellExtension.manifest.layer.State}}
 */
shellExtension.reload.OverrideSettings;


/**
 * Defines a set of data that will be passed around and utilized during the
 * updating of scripts.
 * @typedef {{injectBackgroundScript: boolean,
 *            loadBackgroundScript: boolean,
 *            tabId: number,
 *            url: string}}
 */
shellExtension.reload.Data;


/**
 * Defines the default shellExtension.reload.Data.
 * @type {!shellExtension.reload.Data}
 */
shellExtension.reload.DEFAULT_DATA = {
  injectBackgroundScript: true,
  loadBackgroundScript: true,
  tabId: -1,
  url: ''
};


/**
 * Given a list of layers, go through each one clearing it and then reloading
 * all of its information asynchronously.
 * @param {!Array.<shellExtension.Layer>} layers Array of layers to reload.
 * @param {!Object.<string, shellExtension.reload.OverrideSettings>} userData
 *     This object holds mappings of layer id to user data.  Each layer to be
 *     loaded does not require a mapping, but if it is present then it will
 *     override the values in the loaded content.
 * @param {function()} completeHandler A callback that is fired when the
 *     process is complete.
 * @param {!bite.common.log.Simple} log The log to record errors.
 */
shellExtension.reload.layers = function(layers, userData, completeHandler,
                                        log) {
  var get = function(layer) {
    if (layer.getStatus() == shellExtension.status.Value.BAD_OBJECT) {
      // The only status that can't be loaded is BAD_OBJECT because it has
      // no id.  Send a response that will test false.
      return '';
    }
    return layer.getId();
  };

  var updateLayer = function(layer, success, data) {
    shellExtension.reload.updateLayer_(userData, log, layer, success, data);
  };

  var updateComplete = function(layers) {
    shellExtension.reload.updateLayersComplete_(layers, completeHandler, log);
  };

  var async = bite.common.net.xhr.async;
  async.getMultiple(layers, get, updateLayer, updateComplete);
};


/**
 * Given a list of layers, go through each one and load its content and
 * background scripts asynchronously.
 * @param {!Array.<shellExtension.Layer>} layers The array of layers that need
 *     need loading.
 * @param {function()} completeHandler A callback that is fired when the
 *     process is complete.
 * @param {!bite.common.log.Simple} log The log to record errors.
 * @param {!shellExtension.reload.Data=} opt_data The data to alter the load
 *     process for scripts.
 */
shellExtension.reload.scripts = function(layers, completeHandler, log,
                                         opt_data) {
  // Configure data used during load.
  var data = opt_data || shellExtension.reload.DEFAULT_DATA;
  data.injectBackgroundScript = data.injectBackgroundScript || true;
  data.loadBackgroundScript = data.loadBackgroundScript || true;

  // Prepare handlers.
  var handlers = [];
  for (var i = 0, len = layers.length; i < len; ++i) {
    var layer = layers[i];
    if (layer.getStatus() == shellExtension.status.Value.BAD_OBJECT) {
      continue;
    }

    var state = layer.getState();
    var ondemand = layer.getOndemand();

    var type = shellExtension.data.layer.ScriptType.BACKGROUND;
    if (data.loadBackgroundScript) {
      var bgHandlers = layer.getScriptDataHandlers(type, log);
      handlers = handlers.concat(bgHandlers);
    }

    var csHandlers;
    type = shellExtension.data.layer.ScriptType.CONTENT;
    if (ondemand == shellExtension.manifest.layer.State.OFF) {
      // If ondemand is OFF then inject for any url.
      var urlMatching = bite.common.chrome.extension.urlMatching;
      var allUrls = urlMatching.MatchPatterns.ALL_URLS;
      csHandlers = layer.getScriptDataHandlers(type, log, allUrls);
      handlers = handlers.concat(csHandlers);
    } else if (data.url && data.tabId &&
               state != shellExtension.manifest.layer.State.OFF) {
      // If layer is turned ON and a valid url and tabId are given then load
      // the scripts if the url matches the script's match patterns.
      csHandlers = layer.getScriptDataHandlers(type, log, data.url);
      handlers = handlers.concat(csHandlers);
    }
  }

  // Construct functions used during script retrieval.
  var get = function(handler) {
    return handler.url || null;
  };

  var updateScript = function(handlerObj, success, data) {
    shellExtension.reload.updateScript_(log, handlerObj, success, data);
  };

  var updateScriptComplete = function() {
    shellExtension.reload.updateScriptsComplete_(layers, data, log,
                                                 completeHandler);
  };

  bite.common.net.xhr.async.getMultiple(handlers, get, updateScript,
                                        updateScriptComplete);
};


/**
 * When the request for layer data completes this callback is fired and if
 * successful the data is loaded into the given layer.
 * @param {!Object.<string, shellExtension.reload.OverrideSettings>} userData
 *     This object holds mappings of layer id to user data.  Each layer to be
 *     loaded does not require a mapping, but if it is present then it will
 *     override the values in the loaded content.
 * @param {!bite.common.log.Simple} log The log to record errors.
 * @param {!shellExtension.Layer} layer The layer to be loaded.
 * @param {boolean} success Whether or not the retrieval was successful.
 * @param {string} data The response text.
 * @private
 */
shellExtension.reload.updateLayer_ = function(userData, log, layer, success,
                                              data) {
  /*** Prepare retrieved data ***************************/
  var msgBase = 'ERROR (shellExtension.reload.updateLayer_): ';

  var object;
  try {
    if (!success) {
      throw 'Failed to retrieve layer resource.';
    }

    object = JSON.parse(decodeURIComponent(data));
    !object && (object = {});
  } catch (error) {
    layer.reload(null, log); // Put Layer in a FAILED state.
    return;
  }

  /*** Load object **************************************/
  // Reload the layer with the data given in object.
  layer.reload(/** @type {Object} */ (object), log);

  if (layer.getStatus() == shellExtension.status.Value.FAILED ||
      !layer.getId()) {
    return;
  }

  /*** Process user data ********************************/
  // Change user optional settings to user choices.
  var state = shellExtension.manifest.layer.State;
  var userDataObj = userData[/** @type {string} */ (layer.getId())];
  if (userDataObj) {
    if (userDataObj.state && layer.getState() != state.FIXED) {
      layer.setState(userDataObj.state, log);
    }
    if (userDataObj.ondemand && layer.getOndemand() != state.FIXED) {
      layer.setOndemand(userDataObj.ondemand, log);
    }
  }
};


/**
 * This callback will be fired once set of layers have been loaded.
 * @param {!Array.<shellExtension.Layer>} layers The array of layers that
 *     finished being processed.  This functions does post-processing by
 *     starting the loading of scripts.
 * @param {function()} completeHandler A callback that is fired when the
 *     process is complete.
 * @param {!bite.common.log.Simple} log The log to record errors.
 * @private
 */
shellExtension.reload.updateLayersComplete_ = function(layers, completeHandler,
                                                       log) {
  shellExtension.reload.scripts(layers, completeHandler, log);
};


/**
 * This callback while be fired when a script has finished loading and will
 * call the script's set data function to set the data.
 * @param {!bite.common.log.Simple} log The log to record errors.
 * @param {!Object} handlerObj This object contains the function to be called
 *     with the retrieved data, or null if no data received.  The object format
 *     is {url: string, callback: Function(?string)}.
 * @param {boolean} success Whether or not the data was retrieved.
 * @param {string} data The data retrieved or an error message.
 * @private
 */
shellExtension.reload.updateScript_ = function(log, handlerObj, success,
                                               data) {
  if (!success) {
    if (data) {
      log.add(data);
    }
    log.add('ERROR (shellExtension.reload.updateScript_): Load failed.');
    handlerObj.callback(null);
    return;
  }

  // Set the data for the script.
  handlerObj.callback(data);
};


/**
 * When all the layers that are loading their data are done, this callback is
 * fired.
 * @param {!Array.<shellExtension.Layer>} layers The array of layers that are
 *     loading.
 * @param {!shellExtension.reload.Data} data The data to alter the load process
 *     for scripts.
 * @param {!bite.common.log.Simple} log The log to record errors.
 * @param {function()} completeHandler A callback that is fired when the
 *     process is complete.
 * @private
 */
shellExtension.reload.updateScriptsComplete_ = function(layers, data, log,
                                                        completeHandler) {
  for (var i = 0, len = layers.length; i < len; ++i) {
    var layer = layers[i];
    var status = layer.getStatus();
    var state = layer.getState();
    var ondemand = layer.getOndemand();
    var isOff = state == shellExtension.manifest.layer.State.OFF;

    if (status != shellExtension.status.Value.LOADED || isOff) {
      continue;
    }

    var type = shellExtension.data.layer.ScriptType.BACKGROUND;
    if (data.injectBackgroundScript && !isOff) {
      layer.inject(type, log);
    }

    type = shellExtension.data.layer.ScriptType.CONTENT;
    if (data.url && data.tabId && !isOff) {
      layer.inject(type, log, data.url, data.tabId);
    }
  }

  completeHandler();
};

